#include <linux/errno.h>
#include <linux/types.h>
#include <linux/irq.h>

struct irq_data *irq_domain_get_irq_data(struct irq_domain *domain,
					 unsigned int virq)
{
	struct irq_data *irq_data = irq_get_irq_data(virq);

	return (irq_data && irq_data->domain == domain) ? irq_data : NULL;
}

int irq_domain_set_hwirq_and_chip(struct irq_domain *domain, unsigned int virq,
				  irq_hw_number_t hwirq, struct irq_chip *chip,
				  void *chip_data)
{
	struct irq_data *irq_data = irq_domain_get_irq_data(domain, virq);

	if (!irq_data)
		return -ENOENT;

	irq_data->hwirq = hwirq;
	// irq_data->chip = chip ? chip : &no_irq_chip;
	irq_data->chip = chip;
	irq_data->chip_data = chip_data;

	return 0;
}

void irq_domain_set_info(struct irq_domain *domain, unsigned int virq,
			 irq_hw_number_t hwirq, struct irq_chip *chip,
			 void *chip_data, irq_flow_handler_t handler,
			 void *handler_data, const char *handler_name)
{
	// printf("this is %s(): %d\r\n", __func__, __LINE__);
	irq_domain_set_hwirq_and_chip(domain, virq, hwirq, chip, chip_data);
	__irq_set_handler(virq, handler, 0, handler_name);
	// irq_set_handler_data(virq, handler_data);
}

#define THIS_MODULE (0)

int irq_domain_alloc_descs(int virq, unsigned int cnt, irq_hw_number_t hwirq,
			   int node, const struct irq_affinity_desc *affinity)
{
	unsigned int hint;

	if (virq >= 0) {
		virq = __irq_alloc_descs(virq, virq, cnt, node, THIS_MODULE,
					 affinity);
	} else {
		// hint = hwirq % nr_irqs;
		hint = hwirq % NR_IRQS;
		if (hint == 0)
			hint++;
		virq = __irq_alloc_descs(-1, hint, cnt, node, THIS_MODULE,
					 affinity);
		if (virq <= 0 && hint > 1) {
			virq = __irq_alloc_descs(-1, 1, cnt, node, THIS_MODULE,
						 affinity);
		}
	}

	return virq;
}

static void irq_domain_set_mapping(struct irq_domain *domain,
				   irq_hw_number_t hwirq,
				   struct irq_data *irq_data)
{
	// if (irq_domain_is_nomap(domain))
	// 	return;

	// mutex_lock(&domain->revmap_mutex);
	// if (hwirq < domain->revmap_size)
		// rcu_assign_pointer(domain->revmap[hwirq], irq_data);
		domain->revmap[hwirq] = irq_data;
	// else
	// 	radix_tree_insert(&domain->revmap_tree, hwirq, irq_data);
	// mutex_unlock(&domain->revmap_mutex);
}

int irq_domain_associate(struct irq_domain *domain, unsigned int virq,
			 irq_hw_number_t hwirq)
{
	struct irq_data *irq_data = irq_get_irq_data(virq);
	int ret;

	// if (WARN(hwirq >= domain->hwirq_max,
	// 	 "error: hwirq 0x%x is too large for %s\n", (int)hwirq, domain->name))
	// 	return -EINVAL;
	// if (WARN(!irq_data, "error: virq%i is not allocated", virq))
	// 	return -EINVAL;
	// if (WARN(irq_data->domain, "error: virq%i is already associated", virq))
	// 	return -EINVAL;

	// mutex_lock(&irq_domain_mutex);
	irq_data->hwirq = hwirq;
	irq_data->domain = domain;
	if (domain->ops->map) {
		ret = domain->ops->map(domain, virq, hwirq);
		// if (ret != 0) {
		// 	/*
		// 	 * If map() returns -EPERM, this interrupt is protected
		// 	 * by the firmware or some other service and shall not
		// 	 * be mapped. Don't bother telling the user about it.
		// 	 */
		// 	if (ret != -EPERM) {
		// 		pr_info("%s didn't like hwirq-0x%lx to VIRQ%i mapping (rc=%d)\n",
		// 		       domain->name, hwirq, virq, ret);
		// 	}
		// 	irq_data->domain = NULL;
		// 	irq_data->hwirq = 0;
		// 	mutex_unlock(&irq_domain_mutex);
		// 	return ret;
		// }

		// /* If not already assigned, give the domain the chip's name */
		// if (!domain->name && irq_data->chip)
		// 	domain->name = irq_data->chip->name;
	}

	// domain->mapcount++;
	irq_domain_set_mapping(domain, hwirq, irq_data);
	// mutex_unlock(&irq_domain_mutex);

	// irq_clear_status_flags(virq, IRQ_NOREQUEST);

	return 0;
}

unsigned int irq_create_mapping_affinity(struct irq_domain *domain,
				       irq_hw_number_t hwirq,
				       const struct irq_affinity_desc *affinity)
{
	// struct device_node *of_node;
	int virq;

	// printf("irq_create_mapping(0x%p, 0x%lx)\r\n", domain, hwirq);

	// /* Look for default domain if necessary */
	// if (domain == NULL)
	// 	domain = irq_default_domain;
	// if (domain == NULL) {
	// 	WARN(1, "%s(, %lx) called with NULL domain\n", __func__, hwirq);
	// 	return 0;
	// }
	// printf("-> using domain @%p\r\n", domain);

	// of_node = irq_domain_get_of_node(domain);

	/* Check if mapping already exists */
	// virq = irq_find_mapping(domain, hwirq);
	// if (virq) {
	// 	printf("-> existing mapping on virq %d\n", virq);
	// 	return virq;
	// }

	/* Allocate a virtual interrupt number */
	// virq = irq_domain_alloc_descs(-1, 1, hwirq, of_node_to_nid(of_node),
	// 			      affinity);
	virq = irq_domain_alloc_descs(-1, 1, hwirq, 0, affinity);
	if (virq <= 0) {
		printf("-> virq allocation failed\r\n");
		return 0;
	}

	if (irq_domain_associate(domain, virq, hwirq)) {
		// irq_free_desc(virq);
		return 0;
	}

	printf("irq %lu on domain %s mapped to virtual irq %u\r\n",
		hwirq, "default", virq);

	return virq;
}

struct irq_desc *__irq_resolve_mapping(struct irq_domain *domain,
				       irq_hw_number_t hwirq,
				       unsigned int *irq)
{
	struct irq_desc *desc = NULL;
	struct irq_data *data;

	/* Look for default domain if necessary */
	// if (domain == NULL)
	// 	domain = irq_default_domain;
	// if (domain == NULL)
	// 	return desc;

	// if (irq_domain_is_nomap(domain)) {
	// 	if (hwirq < domain->revmap_size) {
	// 		data = irq_domain_get_irq_data(domain, hwirq);
	// 		if (data && data->hwirq == hwirq)
	// 			desc = irq_data_to_desc(data);
	// 	}

	// 	return desc;
	// }

	// rcu_read_lock();
	/* Check if the hwirq is in the linear revmap. */
	// if (hwirq < domain->revmap_size)
	// 	data = rcu_dereference(domain->revmap[hwirq]);
		data = domain->revmap[hwirq];
	// else
	// 	data = radix_tree_lookup(&domain->revmap_tree, hwirq);

	if (data) {
		desc = irq_data_to_desc(data);
		if (irq)
			*irq = data->irq;
	}

	// rcu_read_unlock();
	return desc;
}

// struct irq_domain *__irq_domain_add(struct fwnode_handle *fwnode, unsigned int size,
// 				    irq_hw_number_t hwirq_max, int direct_max,
// 				    const struct irq_domain_ops *ops,
// 				    void *host_data)
struct irq_domain *__irq_domain_add(unsigned int size,
				    irq_hw_number_t hwirq_max, int direct_max,
				    const struct irq_domain_ops *ops,
				    void *host_data)
{
	// struct irqchip_fwid *fwid;
	struct irq_domain *domain;

	// static atomic_t unknown_domains;

	// if (WARN_ON((size && direct_max) ||
		    // (!IS_ENABLED(CONFIG_IRQ_DOMAIN_NOMAP) && direct_max)))
		// return NULL;

	// domain = kzalloc_node(struct_size(domain, revmap, size),
	// 		      GFP_KERNEL, of_node_to_nid(to_of_node(fwnode)));
	domain = kmalloc(sizeof(struct irq_domain));
	if (!domain)
		return NULL;
	memset(domain, 0, sizeof(struct irq_domain));

	// if (is_fwnode_irqchip(fwnode)) {
	// 	fwid = container_of(fwnode, struct irqchip_fwid, fwnode);

	// 	switch (fwid->type) {
	// 	case IRQCHIP_FWNODE_NAMED:
	// 	case IRQCHIP_FWNODE_NAMED_ID:
	// 		domain->fwnode = fwnode;
	// 		domain->name = kstrdup(fwid->name, GFP_KERNEL);
	// 		if (!domain->name) {
	// 			kfree(domain);
	// 			return NULL;
	// 		}
	// 		domain->flags |= IRQ_DOMAIN_NAME_ALLOCATED;
	// 		break;
	// 	default:
	// 		domain->fwnode = fwnode;
	// 		domain->name = fwid->name;
	// 		break;
	// 	}
	// } else if (is_of_node(fwnode) || is_acpi_device_node(fwnode) ||
	// 	   is_software_node(fwnode)) {
	// 	char *name;

	// 	/*
	// 	 * fwnode paths contain '/', which debugfs is legitimately
	// 	 * unhappy about. Replace them with ':', which does
	// 	 * the trick and is not as offensive as '\'...
	// 	 */
	// 	name = kasprintf(GFP_KERNEL, "%pfw", fwnode);
	// 	if (!name) {
	// 		kfree(domain);
	// 		return NULL;
	// 	}

	// 	strreplace(name, '/', ':');

	// 	domain->name = name;
	// 	domain->fwnode = fwnode;
	// 	domain->flags |= IRQ_DOMAIN_NAME_ALLOCATED;
	// }

	// if (!domain->name) {
	// 	if (fwnode)
	// 		pr_err("Invalid fwnode type for irqdomain\n");
	// 	domain->name = kasprintf(GFP_KERNEL, "unknown-%d",
	// 				 atomic_inc_return(&unknown_domains));
	// 	if (!domain->name) {
	// 		kfree(domain);
	// 		return NULL;
	// 	}
	// 	domain->flags |= IRQ_DOMAIN_NAME_ALLOCATED;
	// }

	// fwnode_handle_get(fwnode);
	// fwnode_dev_initialized(fwnode, true);

	/* Fill structure */
	// INIT_RADIX_TREE(&domain->revmap_tree, GFP_KERNEL);
	// mutex_init(&domain->revmap_mutex);
	domain->ops = ops;
	domain->host_data = host_data;
	domain->hwirq_max = hwirq_max;

	// if (direct_max) {
	// 	size = direct_max;
	// 	domain->flags |= IRQ_DOMAIN_FLAG_NO_MAP;
	// }

	domain->revmap_size = size;

	// irq_domain_check_hierarchy(domain);

	// mutex_lock(&irq_domain_mutex);
	// debugfs_add_domain_dir(domain);
	// list_add(&domain->link, &irq_domain_list);
	// mutex_unlock(&irq_domain_mutex);

	// printf("Added domain %s\n", domain->name);
	return domain;
}
